﻿using IOC.Desgin;
using System;
using System.Collections.Generic;
using System.Linq;
using System.Reflection;
using System.Reflection.Emit;
using System.Text;
using System.Threading.Tasks;

namespace IOC.Proxy
{
    public class DynamictProxy
    {
        public static object CreateProxyObject(Type InterfaceType, Type ImpType)
        {
            return CreateProxyObject(InterfaceType, ImpType, typeof(DefaultIntercept));
        }

        public static object CreateProxyObject(Type InterfaceType, Type ImpType, Type InterceptType, bool inheritMode = true, bool isInterceptAllMethod = true, bool IsSaveDll = false)
        {
            string nameOfAssembly = ImpType.Name + "ProxyAssembly";
            string nameOfModule = ImpType.Name + "ProxyModule";
            string nameOfType = ImpType.Name + "Proxy";
            AssemblyName assemblyName = new AssemblyName(nameOfAssembly);
            ModuleBuilder moduleBuilder = null;
            AssemblyBuilder assemblyBuilder = null;
            if (IsSaveDll)
            {
                assemblyBuilder = AppDomain.CurrentDomain.DefineDynamicAssembly(assemblyName, AssemblyBuilderAccess.RunAndSave);
                moduleBuilder = assemblyBuilder.DefineDynamicModule(nameOfModule, nameOfType + ".dll");
            }
            else
            {
                AssemblyBuilder assembly = AppDomain.CurrentDomain.DefineDynamicAssembly(assemblyName, AssemblyBuilderAccess.Run);
                moduleBuilder = assembly.DefineDynamicModule(nameOfModule);
            }
            TypeBuilder typeBuilder = ((!inheritMode) ? moduleBuilder.DefineType(nameOfType, TypeAttributes.Public, null, new Type[1] { InterfaceType }) : moduleBuilder.DefineType(nameOfType, TypeAttributes.Public, ImpType));
            InjectInterceptor(typeBuilder, ImpType, InterceptType, inheritMode, isInterceptAllMethod);
            Type t = typeBuilder.CreateType();
            if (IsSaveDll)
            {
                assemblyBuilder.Save(nameOfType + ".dll");
            }
            return Activator.CreateInstance(t);
        }

        private static void InjectInterceptor(TypeBuilder typeBuilder, Type ImpType, Type InterceptType, bool inheritMode, bool isInterceptAllMethod)
        {
            ConstructorBuilder constructorBuilder = typeBuilder.DefineConstructor(MethodAttributes.Public, CallingConventions.Standard, null);
            ILGenerator ilOfCtor = constructorBuilder.GetILGenerator();
            FieldBuilder fieldInterceptor = typeBuilder.DefineField("_interceptor", InterceptType, FieldAttributes.Private);
            ilOfCtor.Emit(OpCodes.Ldarg_0);
            ilOfCtor.Emit(OpCodes.Newobj, InterceptType.GetConstructor(new Type[0]));
            ilOfCtor.Emit(OpCodes.Stfld, fieldInterceptor);
            FieldBuilder fieldBeProxy = typeBuilder.DefineField("_beproxy", ImpType, FieldAttributes.Private);
            ilOfCtor.Emit(OpCodes.Ldarg_0);
            ilOfCtor.Emit(OpCodes.Newobj, ImpType.GetConstructor(new Type[0]));
            ilOfCtor.Emit(OpCodes.Stfld, fieldBeProxy);
            ilOfCtor.Emit(OpCodes.Ret);
            MethodInfo[] methodsOfType = (from a in ImpType.GetMethods(BindingFlags.Instance | BindingFlags.Public)
                                          where a.IsVirtual
                                          select a).ToArray();
            string[] ignoreMethodName = new string[4] { "GetType", "ToString", "GetHashCode", "Equals" };
            for (int i = 0; i < methodsOfType.Length; i++)
            {
                bool IsMethodIntercept = false;
                MethodInfo method = methodsOfType[i];
                if (ignoreMethodName.Contains(method.Name) || method.Name.StartsWith("set_") || method.Name.StartsWith("get_"))
                {
                    continue;
                }
                if (isInterceptAllMethod)
                {
                    if (method.GetCustomAttribute(typeof(IgnoreInterceptAttibute)) == null)
                    {
                        IsMethodIntercept = true;
                    }
                }
                else if (method.GetCustomAttribute(typeof(InterceptAttibute)) != null)
                {
                    IsMethodIntercept = true;
                }
                Type[] methodParameterTypes = (from p in method.GetParameters()
                                               select p.ParameterType).ToArray();
                MethodBuilder methodBuilder = typeBuilder.DefineMethod(method.Name, MethodAttributes.Public | MethodAttributes.Virtual, CallingConventions.Standard, method.ReturnType, methodParameterTypes);
                if (method.IsGenericMethod)
                {
                    Type[] Args = method.GetGenericArguments();
                    List<string> GenericArgNames = new List<string>();
                    for (int j = 0; j < Args.Length; j++)
                    {
                        GenericArgNames.Add(Args[j].Name);
                    }
                    GenericTypeParameterBuilder[] DGP = methodBuilder.DefineGenericParameters(GenericArgNames.ToArray());
                    for (int j = 0; j < DGP.Length; j++)
                    {
                        DGP[j].SetBaseTypeConstraint(Args[j].BaseType);
                        DGP[j].SetInterfaceConstraints(Args[j].GetInterfaces());
                    }
                }
                ILGenerator ilOfMethod = methodBuilder.GetILGenerator();
                LocalBuilder methodresult = ilOfMethod.DeclareLocal(typeof(object));
                if (IsMethodIntercept)
                {
                    LocalBuilder parameters = ilOfMethod.DeclareLocal(typeof(object[]));
                    ilOfMethod.Emit(OpCodes.Ldc_I4, methodParameterTypes.Length);
                    ilOfMethod.Emit(OpCodes.Newarr, typeof(object));
                    ilOfMethod.Emit(OpCodes.Stloc, parameters);
                    for (int j = 0; j < methodParameterTypes.Length; j++)
                    {
                        ilOfMethod.Emit(OpCodes.Ldloc, parameters);
                        ilOfMethod.Emit(OpCodes.Ldc_I4, j);
                        ilOfMethod.Emit(OpCodes.Ldarg, j + 1);
                        ilOfMethod.Emit(OpCodes.Stelem_Ref);
                    }
                    ilOfMethod.Emit(OpCodes.Ldarg_0);
                    ilOfMethod.Emit(OpCodes.Ldfld, fieldInterceptor);
                    if (inheritMode)
                    {
                        ilOfMethod.Emit(OpCodes.Ldarg_0);
                    }
                    else
                    {
                        ilOfMethod.Emit(OpCodes.Ldarg_0);
                        ilOfMethod.Emit(OpCodes.Ldfld, fieldBeProxy);
                    }
                    ilOfMethod.Emit(OpCodes.Ldstr, method.Name);
                    ilOfMethod.Emit(OpCodes.Ldloc, parameters);
                    ilOfMethod.Emit(OpCodes.Callvirt, InterceptType.GetMethod("Before"));
                }
                LocalBuilder localstart = ilOfMethod.DeclareLocal(typeof(DateTime));
                ilOfMethod.Emit(OpCodes.Call, typeof(DateTime).GetMethod("get_Now"));
                ilOfMethod.Emit(OpCodes.Stloc, localstart);
                if (inheritMode)
                {
                    ilOfMethod.Emit(OpCodes.Ldarg_0);
                    ilOfMethod.Emit(OpCodes.Call, ImpType.GetMethod(method.Name));
                }
                else
                {
                    ilOfMethod.Emit(OpCodes.Ldarg_0);
                    ilOfMethod.Emit(OpCodes.Ldfld, fieldBeProxy);
                    for (int j = 0; j < methodParameterTypes.Length; j++)
                    {
                        ilOfMethod.Emit(OpCodes.Ldarg, j + 1);
                    }
                    ilOfMethod.Emit(OpCodes.Callvirt, ImpType.GetMethod(method.Name));
                }
                LocalBuilder localend = ilOfMethod.DeclareLocal(typeof(DateTime));
                ilOfMethod.Emit(OpCodes.Call, typeof(DateTime).GetMethod("get_Now"));
                ilOfMethod.Emit(OpCodes.Stloc, localend);
                if (IsMethodIntercept)
                {
                    if (method.ReturnType == typeof(void))
                    {
                        ilOfMethod.Emit(OpCodes.Ldnull);
                    }
                    else
                    {
                        ilOfMethod.Emit(OpCodes.Box, method.ReturnType);
                    }
                    ilOfMethod.Emit(OpCodes.Stloc, methodresult);
                    ilOfMethod.Emit(OpCodes.Ldarg_0);
                    ilOfMethod.Emit(OpCodes.Ldfld, fieldInterceptor);
                    if (inheritMode)
                    {
                        ilOfMethod.Emit(OpCodes.Ldarg_0);
                    }
                    else
                    {
                        ilOfMethod.Emit(OpCodes.Ldarg_0);
                        ilOfMethod.Emit(OpCodes.Ldfld, fieldBeProxy);
                    }
                    ilOfMethod.Emit(OpCodes.Ldstr, method.Name);
                    ilOfMethod.Emit(OpCodes.Ldloc, methodresult);
                    ilOfMethod.Emit(OpCodes.Ldloc, localstart);
                    ilOfMethod.Emit(OpCodes.Ldloc, localend);
                    ilOfMethod.Emit(OpCodes.Callvirt, InterceptType.GetMethod("After"));
                    ilOfMethod.Emit(OpCodes.Ldloc, methodresult);
                }
                if (method.ReturnType == typeof(void))
                {
                    ilOfMethod.Emit(OpCodes.Pop);
                }
                else if (method.ReturnType.IsValueType)
                {
                    ilOfMethod.Emit(OpCodes.Unbox_Any, method.ReturnType);
                }
                else
                {
                    ilOfMethod.Emit(OpCodes.Castclass, method.ReturnType);
                }
                ilOfMethod.Emit(OpCodes.Ret);
            }
        }
    }
}
